package com.joinway.platform.utils;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.stream.Collectors;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.app.VelocityEngine;
import org.springframework.ui.velocity.VelocityEngineUtils;

import com.joinway.framework.bean.domain.constant.SqlConstants;
import com.joinway.framework.extension.groovy.DomainUtils;
import com.joinway.framework.extension.utils.DataUtils;
import com.joinway.framework.text.java.JavaBuilder;
import com.joinway.framework.text.java.JavaField;
import com.joinway.framework.text.sql.DdlParser;
import com.joinway.framework.text.sql.bean.Column;
import com.joinway.framework.text.sql.bean.Table;
import com.joinway.framework.text.ui.PageField;
import com.joinway.framework.text.ui.UIBuilder;
import com.joinway.platform.bean.AdminPageConfig;
import com.joinway.platform.bean.AdminPageItem;
import com.joinway.platform.bean.ColumnField;
import com.joinway.platform.bean.EasyUIPageField;
import com.joinway.platform.bean.QueryOrder;
import com.joinway.platform.bean.RadioItem;

public final class Ddl2CodeBuilder {

	final static VelocityEngine velocityEngine;

//	final static String datagridTemplate = "template/ui/datagrid.jsp";
	final static String mapperTemplate = "template/xml/mapper.xml";
	final static String domainTemplate = "template/groovy/domain.groovy";
	final static String daoTemplate = "template/java/dao.java";
	final static String daoRepositoryTemplate = "template/java/repository.java";
	final static String tableServiceTemplate = "template/java/tableService.java";
	final static String adminPageTemplate = "template/ui/datagrid.jsp";
	
	final static String ENCODE = "UTF-8";
	
	static{
		Properties props = new Properties();
		props.setProperty("resource.loader", "class");
		props.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
		velocityEngine = new VelocityEngine(props);
	}

	@Deprecated DdlEntity entity;
	
	Table table;
	
	public Ddl2CodeBuilder(String sql) throws Exception {
		table = DdlParser.parse(sql);
		entity = new DdlEntity(sql);
	}
	
	public void buildDomainBean(String module, String outFile, Map<String, String> masks, Map<String, String> formats, Map<String, String> dates) throws Exception {
		Map<String, Object> model = new HashMap<>();
		
		model.put("moduleName", module);
		
		model.put("ClassName", StringUtils.capitalize(DomainUtils.convertToDomainFieldName(table.getName())));
		
		List<Column> primaryColumns = table.getPrimaryColumns();
		List<JavaField> fields = JavaBuilder.buildJavaFields(table.getColumns());
		
		Set<String> imports = new TreeSet<>(new Comparator<String>(){
			@Override
			public int compare(String s1, String s2) {
				return s1.compareTo(s2);
			}
		});
		
		boolean defaultId = true;
		
		if(CollectionUtils.isNotEmpty(primaryColumns) &&
			primaryColumns.stream().filter(c -> SqlConstants.Key.Id.equals(c.getName().toLowerCase())).count() > 0){
			
			if(DataUtils.getSize(dates) > 0){
				String baseName = "UpdatableDomain";
				model.put("BaseClass", baseName);
				imports.add("com.joinway.framework.bean.domain." + baseName);
			}else{
				String baseName = "AbstractDomain";
				model.put("BaseClass", baseName);
				imports.add("com.joinway.framework.bean.domain." + baseName);
			}
			
			fields = fields.stream().filter(f -> !SqlConstants.Key.Id.equals(f.getName())).collect(Collectors.toList());
		}else{
			String baseName = "BaseBean";
			model.put("BaseClass", baseName);
			imports.add("com.joinway.framework.bean." + baseName);
			defaultId = false;
		}
		
		Optional<Column> op = primaryColumns.stream().filter(c -> c.isAutoIncrement() && c.isPrimary()).findFirst();
		String autoPKField = "";
		if(op.isPresent()){
			autoPKField = DomainUtils.convertToDomainFieldName(op.get().getName());
		}
		for(JavaField field : fields){
			if(!defaultId && autoPKField.equals(field.getName())){
				field.getAnnotations().add("@Id");
				imports.add("javax.persistence.Id");
			}
			
			if(masks.containsKey(field.getName())){
				String mask = masks.get(field.getName());
				field.getAnnotations().add(mask);
				imports.add("com.joinway.framework.bean.logging.annotation." + mask.substring(1));
			}
			
			if(formats.containsKey(field.getName())){
				String format = formats.get(field.getName());
				field.getAnnotations().add(format);
				imports.add("com.joinway.framework.bean.domain.type.PersistCase");
				imports.add("com.joinway.framework.bean.domain.annonation.PersistFormat");
			}
			
			if(dates.containsKey(field.getName())){
				String ann = dates.get(field.getName());
				field.getAnnotations().add(ann);
				imports.add("com.joinway.framework.bean.domain.annonation." + ann.substring(1));
			}
			
		}
		
		model.put("fields", fields);
		model.put("imports", imports);
		
		writeToFile(model, domainTemplate, outFile);
	}
	
	public void buildMyBatisXml(String mapperModule, String domainModule, List<String> columns, Map<String, String> conditions, String outFile) throws Exception {
		Map<String, Object> model = new HashMap<>();
		
		model.put("mapperModuleName", mapperModule);
		model.put("domainModuleName", domainModule);
		model.put("DomainName", entity.getDomainName());
		model.put("TableName", entity.getTableName());
		model.put("idField", entity.getIdField());
		model.put("fields", entity.getNonDefaultIdFields());
		
		List<ColumnField> keys = constructConditions(columns, conditions);
		model.put("keys", keys);
		
		writeToFile(model, mapperTemplate, outFile);	
	}
	
	public void buildDao(String mapperModule, String domainModule, List<String> columns, Map<String, String> conditions, String outFile) throws Exception {
		Map<String, Object> model =buildCommonDao(mapperModule, domainModule);
		
		List<ColumnField> fields = constructConditions(columns, conditions);
		resetFieldType(fields);
		model.put("fields", fields);
		
		Set<String> imports = DdlEntity.getImports(fields, ImportFor.Type);
		imports.remove("java.util.Date");	// remove date nevertheless contains or not
		model.put("imports", imports);

		writeToFile(model, daoTemplate, outFile);	
	}
	
	public void buildDaoRepository(String mapperModule, String domainModule, List<String> columns, Map<String, String> conditions, String outFile) throws Exception {
		Map<String, Object> model = buildCommonDao(mapperModule, domainModule);
		
		List<ColumnField> fields = constructConditions(columns, conditions);
		resetFieldType(fields);
		model.put("fields", fields);

		model.put("imports", DdlEntity.getImports(fields, ImportFor.Type));
		
		writeToFile(model, daoRepositoryTemplate, outFile);	
	}
	
	public void buildAdminPage(List<AdminPageItem> items, AdminPageConfig config, String outFile) throws Exception {
		Map<String, Object> model = new HashMap<>();
		
		String tableName = DomainUtils.convertToDomainFieldName(table.getName());
		model.put("table", tableName);
		model.put("Table", StringUtils.capitalize(tableName));
		
		List<QueryOrder> orders = new ArrayList<>();
		for(AdminPageItem item : items){
			if(!StringUtils.isBlank(item.getOrder())){
				orders.add(new QueryOrder(DomainUtils.convertToDomainFieldName(item.getColumn()), item.getOrder(), item.getOrderNo()));
			}
		}
		
		if(CollectionUtils.isNotEmpty(orders)){
			Collections.sort(orders, new Comparator<QueryOrder>(){
				@Override
				public int compare(QueryOrder o1, QueryOrder o2) {
					return o1.getNo() - o2.getNo();
				}
			});
			model.put("orders", orders);
		}
		
		// construct query fields
		List<PageField> queryFields = new ArrayList<>();
		List<Column> columns = table.getColumns();
		for(AdminPageItem item : items){
			if(item.isQuery()){
				PageField field = new PageField();
				field.setName(item.getColumn());
				
				for(Column column : columns){
					if(DomainUtils.convertToDomainFieldName(column.getName()).equals(item.getColumn())){
						field.setType(UIBuilder.buildFieldType(JavaBuilder.buildJavaType(column)));
						break;
					}
				}
				
				queryFields.add(field);
			}
		}
		
		model.put("qfields", queryFields);
		
		// construct auto increment id name
		List<Column> primaryColumns = table.getPrimaryColumns();
		List<String> idFields = new ArrayList<>();
		String autoIdField = "";
		for(Column column : primaryColumns){
			if(column.isAutoIncrement() || column.isPrimary()){
				idFields.add("'" + DomainUtils.convertToDomainFieldName(column.getName()) + "'");
			}
			if(column.isAutoIncrement() && column.isPrimary()){
				autoIdField = DomainUtils.convertToDomainFieldName(column.getName());
				model.put("autoIdField", autoIdField);
			}
		}
		if(idFields.size() > 0){
			model.put("idFields", StringUtils.join(idFields, ","));
		}
		
		// construct timestamp and editor fields
		List<String> timestampFields = new ArrayList<>();
		List<String> ctimestampFields = new ArrayList<>();
		List<String> editorFields = new ArrayList<>();
		List<String> creatorFields = new ArrayList<>();
		for(AdminPageItem item : items){
			if("Timestamp".equalsIgnoreCase(item.getCategory())){
				timestampFields.add("'" + item.getColumn() + "'");
			}
			if("CTimestamp".equalsIgnoreCase(item.getCategory())){
				ctimestampFields.add("'" + item.getColumn() + "'");
			}			
			if("Editor".equalsIgnoreCase(item.getCategory())){
				editorFields.add("'" + item.getColumn() + "'");
			}
			if("Creator".equalsIgnoreCase(item.getCategory())){
				creatorFields.add("'" + item.getColumn() + "'");
			}
		}
		if(ctimestampFields.size() > 0){
			model.put("ctt", StringUtils.join(ctimestampFields, ","));
		}
		if(timestampFields.size() > 0){
			model.put("tt", StringUtils.join(timestampFields, ","));
		}
		if(editorFields.size() > 0){
			model.put("ed", StringUtils.join(editorFields, ","));
		}
		if(creatorFields.size() > 0){
			model.put("ced", StringUtils.join(creatorFields, ","));
		}
		
		// construct easyui page field
		List<EasyUIPageField> fields = new ArrayList<>();
		for(AdminPageItem item : items){
			EasyUIPageField field = new EasyUIPageField();
			
			field.setName(item.getColumn());
			
			field.setVisible(item.isVisible());
			field.setText(StringUtils.isBlank(item.getText()) ? item.getColumn() : item.getText());
			field.setEditable(item.isEditable());
			field.setMin(StringUtils.trimToNull(item.getMin()));
			field.setMax(StringUtils.trimToNull(item.getMax()));
			field.setPrecision(StringUtils.trimToNull(item.getPrecision()));
			field.setOption(getEasyUIInputFieldOption(item));
			field.setCategory(item.getCategory());
			
			for(Column column : primaryColumns){
				if(column.isAutoIncrement() && DomainUtils.convertToDomainFieldName(column.getName()).equals(item.getColumn())){
					field.setAutoId(true);
					break;
				}
			}
			
			field.setRadios(getRadios(item));
			
			fields.add(field);
		}
		model.put("fields", fields);
		
		
		// construct page layout params
		model.put("maxColumn", config.getMaxColumn());
		
		List<EasyUIPageField> conditions = new ArrayList<>();
		for(AdminPageItem item : items){
			if(item.isQuery()){
				EasyUIPageField field = new EasyUIPageField();
				
				for(Column column : columns){
					if(column.getName().equals(item.getColumn())){
						field.setType(UIBuilder.buildFieldType(JavaBuilder.buildJavaType(column)));
						break;
					}
				}
	
				field.setName(item.getColumn());
				field.setText(StringUtils.isBlank(item.getText()) ? item.getColumn() : item.getText());
				field.setOption(getEasyUIQueryFieldOption(item));
				field.setCategory(item.getCategory());
				field.setRadios(getRadios(item));
				
				conditions.add(field);
			}
		}
		model.put("conditions", conditions);
		
		
//		model.put("rnd", String.valueOf(Math.abs(new Random().nextInt())).substring(0, 4));
		model.put("rnd", "1" + StringUtils.split(UUID.randomUUID().toString(), "-")[0]);
		
//		List<ColumnField> columnFields = new ArrayList<>();
//		String idName = "";
//		for(ColumnField field : columnFields){
//			if(AutoCodeHelper.matched(field, items)){
//				Ddl2CodeColumn column = new Ddl2CodeColumn();
//				column.setName(field.getName());
//				column.setId(field.isIdKey());
//				
//				AutoCodeHelper.initDdl2CodeColumn(column, field, items);
//				
//				fields.add(column);
//				
//				if("Timestamp".equalsIgnoreCase(column.getCategory())){
//					timestampFields.add(column.getName());
//				}
//				if("Editor".equals(column.getCategory())){
//					editorFields.add(column.getName());
//				}
//				
//				if(column.isQuery()){
//					conditions.add(column);
//				}
//			}
//			
//			if(field.isIdKey()){
//				idName = field.getName();
//			}
//		}
//		
//		model.put("add", config.isAdd());
//		model.put("update", config.isUpdate());
//		model.put("clone", config.isClone());
//		model.put("delete", config.isDelete());
//		model.put("export", config.isExport());
//		model.put("emport", config.isEmport());
////		model.put("format", button.getFormat());
//		
//		model.put("query", fields.stream().filter(f -> f.isQuery()).count() > 0);
		
		
//		if(CollectionUtils.isNotEmpty(timestampFields)){
//			String array = "";
//			for(String s : timestampFields){
//				array += "'" + s + "',";
//			}
//			model.put("tt", array.substring(0, array.length() - 1));
//		}
		
//		if(CollectionUtils.isNotEmpty(editorFields)){
//			String array = "";
//			for(String s : editorFields){
//				array += "'" + s + "',";
//			}
//			model.put("ed", array.substring(0, array.length() - 1));
//		}
		
		writeToFile(model, adminPageTemplate, outFile);
	}
	
	public void buildTableService(AdminPageConfig config, String outFile) throws Exception {
		Map<String, Object> model = new HashMap<>();
		
		model.put("tableName", entity.getTableName());
		
		String domainName = entity.getDomainName();
		model.put("table", StringUtils.uncapitalize(domainName));
		model.put("Table", domainName);
		
		model.put("module", config.getModule());
		
		writeToFile(model, tableServiceTemplate, outFile);
	}
	
	List<RadioItem> getRadios(AdminPageItem item){
		List<RadioItem> list = null;
		
		if(StringUtils.isNotBlank(item.getList())){
			String[] elements = item.getList().trim().split("\\s+");
			list = new ArrayList<>();
			
			for(int i = 0; i < elements.length; i++){
				String[] parts = elements[i].split(":|：|-");
				String value = parts[0];
				String text = parts.length > 1 ? parts[1] : value;
				RadioItem radio = new RadioItem(text, value);
				
				list.add(radio);
			}
		}		
		
		return list;
	}
	
	String getEasyUIQueryFieldOption(AdminPageItem item){
		List<String> options = new ArrayList<>();
		
		if(StringUtils.isNotBlank(item.getMin())){
			options.add("min:" + item.getMin());
		}
		if(StringUtils.isNotBlank(item.getMax())){
			options.add("max:" + item.getMax());
		}
		if(StringUtils.isNotBlank(item.getPrecision())){
			options.add("precision:" + item.getPrecision());
		}
		
		return options.size() > 0 ? StringUtils.join(options, ",") : "";
	}
	
	String getEasyUIInputFieldOption(AdminPageItem item){
		String option = getEasyUIQueryFieldOption(item);
		
		List<String> options = new ArrayList<>();
		options.add("required:" + item.isRequired());
		switch(item.getCategory()){
			case "Email":
				options.add("validType:'email'");
				break;
			case "Mobile":
				options.add("validType:'mobile'");
				break;
			case "Zip":
				options.add("validType:'zip'");
				break;
		}
		
		if(option.length() > 0){
			option += ",";
		}
		
		option += options.size() > 0 ? StringUtils.join(options, ",") : "";
		
		return option;
	}
	
	Map<String, Object> buildCommonDomainBean(String module, String outFile) throws Exception {
		Map<String, Object> model = new HashMap<>();
		
		model.put("moduleName", module);
		model.put("ClassName", entity.getDomainName());
		model.put("idField", entity.getIdField());
		
		return model;
	}

	Map<String, Object> buildCommonDao(String mapperModule, String domainModule){
		Map<String, Object> model = new HashMap<>();
		
		model.put("mapperModuleName", mapperModule);
		model.put("domainModuleName", domainModule);
		model.put("DomainName", entity.getDomainName());

		return model;
	}
	
	List<ColumnField> constructConditions(List<String> columns, Map<String, String> conditions){
		List<ColumnField> fields = Arrays.asList(
				entity.getNonDefaultIdFields().stream()
					.filter(f -> columns.contains(f.getColumn()))
					.toArray(ColumnField[]::new)
			);
			
		fields.stream().forEach(k -> k.setCondition(conditions.get(k.getColumn())));
		
		return DataUtils.clone(fields);
	}
	
	void resetFieldType(List<ColumnField> fields){
		for(ColumnField field : fields){
			field.setType(AutoCodeHelper.getJavaType(field));
		}
	}
	
	void writeToFile(Map<String, Object> model, String template, String file) throws Exception {
		Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), ENCODE)); 
		VelocityEngineUtils.mergeTemplate(velocityEngine, template, ENCODE, model, writer);
		writer.close();
	}
}
